package cn.apotato.security.handler;

import cn.apotato.common.base.controller.BaseController;
import cn.apotato.common.base.enums.BaseControllerMethod;
import cn.apotato.common.core.annotation.MenuName;
import cn.apotato.common.core.utils.SpringUtils;
import cn.apotato.common.core.utils.StringUtils;
import cn.apotato.common.model.system.SysMenu;
import cn.apotato.common.model.system.enums.SysMenuOpenType;
import cn.apotato.common.model.system.enums.SysMenuStatus;
import cn.apotato.common.model.system.enums.SysMenuType;
import cn.apotato.modules.redis.service.RedisService;
import cn.apotato.security.annotation.RequiresPermissions;
import cn.apotato.security.annotation.RequiresRoles;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.HashUtil;
import com.alibaba.fastjson2.JSON;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.ServletContext;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

import static cn.apotato.common.core.constant.SecurityConstants.PERMISSION_REDIS_KEY;

/**
 * 收集权限处理程序
 *
 * @author 胡晓鹏
 * @date 2023/06/02
 */
@Component
public class CollectPermissionsHandler {
    private final RedisService redisService;
    private String contextPath = "/";

    public CollectPermissionsHandler(RedisService redisService, ServletContext servletContext) {
        this.redisService = redisService;
        if (servletContext != null) {
            contextPath = servletContext.getContextPath();
        }
    }

    public void collect() throws NoSuchMethodException {
        // 获取所有 controller
        Set<Object> controllers = getControllers();
        for (Object controllerName : controllers) {
            Object controller = SpringUtils.getBean(String.valueOf(controllerName));
            if (controller == null) {
                continue;
            }
            Class<?> aClass = controller.getClass();
            Annotation[] annotations = aClass.getAnnotations();
            MenuName menuNameA = aClass.getAnnotation(MenuName.class);
            if (menuNameA == null) {
                continue;
            }
            // 获取controller路径
            RequestMapping requestMapping = aClass.getAnnotation(RequestMapping.class);

            String menuName = menuNameA.name();
            String permission = JSON.toJSONString(menuNameA.permission());


            if (StringUtils.isEmpty(contextPath)) {
                contextPath = "/";
            }
            String path = getPath(contextPath + getPath(getMappingValue(requestMapping)));

            // 创建菜单
            SysMenu sysMenu = SysMenu.builder()
                    .name(menuName)
                    .type(SysMenuType.MENU.value)
                    .url(path)
                    .permission(permission)
                    .openType(SysMenuOpenType.NEW.value)
                    .status(SysMenuStatus.DISPLAY.value)
                    .parentId(0L)
                    .build();
            sysMenu.setUniqueId((long) HashUtil.javaDefaultHash(sysMenu.getUrl() + sysMenu.getRequestMethod()));

            // 如果继承了 BaseController 获取CRUD 方法
            Class<?> superclass = aClass.getSuperclass();
            // 创建按钮权限
            if (superclass == BaseController.class) {
                List<SysMenu> menuList = getMenuList(superclass, sysMenu);
                sysMenu.setChildren(menuList);
            }

            List<SysMenu> menuList = getMenuList(aClass, menuName, permission, sysMenu, superclass);
            if (CollUtil.isNotEmpty(menuList)) {
                sysMenu.getChildren().addAll(menuList);
            }

            // 发送到数据库
            redisService.pushMessage(PERMISSION_REDIS_KEY, sysMenu);
        }

    }

    /**
     * 获取菜单列表
     *
     * @param aClass     一个类
     * @param menuName   菜单名称
     * @param permission 许可
     * @param sysMenu    系统菜单
     * @param superclass 超类
     * @return {@link List}<{@link SysMenu}>
     */
    private List<SysMenu> getMenuList(Class<?> aClass, String menuName, String permission, SysMenu sysMenu, Class<?> superclass) {
        // 如果没有继承 BaseController
        List<SysMenu> menuList = new ArrayList<>();
        Method[] methods = aClass.getMethods();
        for (Method method : methods) {
            // 过滤crud 方法
            if (superclass == BaseController.class && BaseControllerMethod.getMethod(method.getName()) != null) {
                continue;
            }
            // 获取请求路径
            String requestPath = getRequestMappingValue(method);
            if (StringUtils.isBlank(requestPath)) {
                continue;
            }
            requestPath = getPath(sysMenu.getUrl() + getPath(requestPath));
            // 获取权限
            RequiresPermissions permissions = method.getAnnotation(RequiresPermissions.class);
            RequiresRoles roles = method.getAnnotation(RequiresRoles.class);
            if (permissions == null && roles == null) {
                continue;
            }

            if (permissions != null && roles != null) {
                throw new RuntimeException("@RequiresPermissions and @RequiresRoles can only have one ！");
            }

            String permission1 = null;
            String menuName1 = null;
            if (permissions != null && StringUtils.isNotEmpty(permissions.value())) {
                permission1 = JSON.toJSONString(permissions.value());
                menuName1 = permissions.name();
            }
            if (roles != null && StringUtils.isNotEmpty(roles.value())) {
                permission1 = JSON.toJSONString(roles.value());
                menuName1 = roles.name();
            }

            if (StringUtils.isEmpty(permission1)) {
                permission1 = permission;
            }
            if (StringUtils.isEmpty(menuName1)) {
                menuName1 = menuName;
            }
            SysMenu btn = SysMenu.builder()
                    .name(menuName1)
                    .type(SysMenuType.BUTTON.value)
                    .url(requestPath)
                    .permission(permission1)
                    .openType(SysMenuOpenType.SINGLE.value)
                    .status(SysMenuStatus.HIDE.value)
                    .parentId(sysMenu.getId())
                    .build();
            btn.setUniqueId((long) HashUtil.javaDefaultHash(btn.getUrl() + btn.getRequestMethod()));
            menuList.add(btn);
        }
        return menuList;
    }


    private String getRequestMappingValue(Method method) {
        String path = null;
        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
        if (requestMapping != null) {
            path = getMappingValue(requestMapping);
        }
        GetMapping getMapping = method.getAnnotation(GetMapping.class);
        if (getMapping != null) {
            path = getMappingValue(getMapping);
        }
        PutMapping putMapping = method.getAnnotation(PutMapping.class);
        if (putMapping != null) {
            path = getMappingValue(putMapping);
        }
        PostMapping postMapping = method.getAnnotation(PostMapping.class);
        if (postMapping != null) {
            path = getMappingValue(postMapping);
        }
        PatchMapping patchMapping = method.getAnnotation(PatchMapping.class);
        if (patchMapping != null) {
            path = getMappingValue(patchMapping);
        }
        DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
        if (deleteMapping != null) {
            path = getMappingValue(deleteMapping);
        }
        return getPath(path);
    }

    private String getMappingValue(RequestMapping requestMapping) {
        String path1 = null;
        String[] values1 = requestMapping.value();
        String[] paths1 = requestMapping.path();
        if (values1.length > 0) {
            path1 = values1[0];
        }
        if (StringUtils.isBlank(path1) && paths1.length > 0) {
            path1 = paths1[0];
        }
        return path1;
    }

    private String getMappingValue(GetMapping requestMapping) {
        String path1 = null;
        String[] values1 = requestMapping.value();
        String[] paths1 = requestMapping.path();
        if (values1.length > 0) {
            path1 = values1[0];
        }
        if (StringUtils.isBlank(path1) && paths1.length > 0) {
            path1 = paths1[0];
        }
        return path1;
    }

    private String getMappingValue(PutMapping requestMapping) {
        String path1 = null;
        String[] values1 = requestMapping.value();
        String[] paths1 = requestMapping.path();
        if (values1.length > 0) {
            path1 = values1[0];
        }
        if (StringUtils.isBlank(path1) && paths1.length > 0) {
            path1 = paths1[0];
        }
        return path1;
    }

    private String getMappingValue(PostMapping requestMapping) {
        String path1 = null;
        String[] values1 = requestMapping.value();
        String[] paths1 = requestMapping.path();
        if (values1.length > 0) {
            path1 = values1[0];
        }
        if (StringUtils.isBlank(path1) && paths1.length > 0) {
            path1 = paths1[0];
        }
        return path1;
    }

    private String getMappingValue(PatchMapping requestMapping) {
        String path1 = null;
        String[] values1 = requestMapping.value();
        String[] paths1 = requestMapping.path();
        if (values1.length > 0) {
            path1 = values1[0];
        }
        if (StringUtils.isBlank(path1) && paths1.length > 0) {
            path1 = paths1[0];
        }
        return path1;
    }

    private String getMappingValue(DeleteMapping requestMapping) {
        String path1 = null;
        String[] values1 = requestMapping.value();
        String[] paths1 = requestMapping.path();
        if (values1.length > 0) {
            path1 = values1[0];
        }
        if (StringUtils.isBlank(path1) && paths1.length > 0) {
            path1 = paths1[0];
        }
        return path1;
    }

    public String getPath(String path) {
        if (StringUtils.isNotBlank(path)) {
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            if (path.endsWith("/")) {
                path = path.substring(0, path.length() - 1);
            }
        }
        return path;
    }

    /**
     * 把菜单列表
     *
     * @param clazz clazz
     * @return {@link List}<{@link SysMenu}>
     * @throws NoSuchMethodException 没有这样方法异常
     */
    public List<SysMenu> getMenuList(Class<?> clazz, SysMenu parentMenu) throws NoSuchMethodException {
        List<SysMenu> menuList = new ArrayList<SysMenu>();
        for (BaseControllerMethod md : BaseControllerMethod.methodList()) {
            SysMenu sysMenu = SysMenu.builder()
                    .name(parentMenu.getName() + "-" + md.name)
                    .url(parentMenu.getUrl() + md.url)
                    .requestMethod(md.method)
                    .type(SysMenuType.BUTTON.value)
                    .openType(SysMenuOpenType.SINGLE.value)
                    .status(SysMenuStatus.HIDE.value)
                    .permission(parentMenu.getPermission())
                    .parentId(parentMenu.getId())
                    .build();
            sysMenu.setUniqueId((long) HashUtil.javaDefaultHash(sysMenu.getUrl() + sysMenu.getRequestMethod()));
            menuList.add(sysMenu);
        }
        return menuList;
    }


    /**
     * 得到控制器
     *
     * @return {@link Set}<{@link Object}>
     */
    public Set<Object> getControllers() {
        RequestMappingHandlerMapping mapping = SpringUtils.getBean(RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();

        Set<Object> controllers = new HashSet<>();
        for (HandlerMethod method : handlerMethods.values()) {
            Object controller = method.getBean();
            controllers.add(controller);
        }
        return controllers;
    }
}
